/**
 * Copyright (c) 2009, Nathan Bubna
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 * This plugin exists to make it trivial to notify your users that
 * things are in progress.  The typical case is waiting for an
 * AJAX call to finish loading.  Just call
 * 
 *   $.loading();
 *
 * to toggle a page-wide message on and off, or you can call
 *
 *   $('#foo').loading()
 *
 * to do the same, but locate the message within a specific element(s).
 *
 * You can change any of the default options by altering the $.loading
 * properties.  These can also be overridden per-call by passing in
 * an options object with the overriding properties. Be sure to check out
 * the provided demo for an easy overview of the most commonly used
 * options.  Of course, everything in this plugin is easy to override in
 * that same manner.
 *
 * Of particular note here is that it is easy to plug in additional
 * "pulse" effects.  Just add an object with a 'run' function to $.loading
 * under the desired effects name, then use it by doing:
 * 
 *   $.loading({pulse: 'myeffect'});
 *
 * If you add an 'end' function to that same object, then the end function
 * will be called when the loading message is turned off.  See the four
 * included effects for examples.
 *
 * Contributions, bug reports and general feedback on this is welcome.
 *
 * @version 1.0.1
 * @name loading
 * @cat Plugins/loading
 * @author Nathan Bubna
 */
;(function($) {

    var L = $.loading = function(opts) {
        opts = $.extend({}, L.pageOptions, opts);
        return $(L.pageSelect).loading(opts);
    };
    $.fn.loading = function(opts) {
        if (opts) {
            // default pulse to off if doing an img
            if (opts.img && !opts.pulse) opts.pulse = false;
            opts = $.extend(true, {}, L, opts);
        }
        return this.each(function() {
            L.toggle.call($(this), $.extend({}, opts));
        });
    };

    $.extend(L, {
        version: "1.0.1",
        // general properties
        html: '<div></div>',
        css: { position: 'absolute', 'white-space': 'nowrap' },
        classname: 'loading',
        withImg: 'img',
        text: 'Loading...',
        img: null,
        align: 'top-left',
        pulse: 'working',
        pageSelect: $.browser.msie ? 'body' : 'html',
        pageOptions: { align: 'top-center' },

        // pulse plugin properties
        working: {
            time: 10000,
            text: 'Still Working...',
            run: function(opts) {
                var w = opts.working, self = this;
                opts.timeout = setTimeout(function() {
                    self.text(w.text);
                    opts.place.call(opts.element, opts);
                }, w.time);
            }
        },
        fade: {
            time: 700,
            speed: 'slow',
            run: function(opts) {
                var s = opts.fade.speed, self = this;
                opts.interval = setInterval(function() {
                    self.fadeOut(s).fadeIn(s);
                }, opts.fade.time);
            }
        },
        ellipsis: {
            time: 300,
            run: function(opts) {
                this.css('width', $.boxModel ? this.width() : this.innerWidth());
                var t = opts.text, x = t.indexOf('.'), i = x<0 ? t.length : x, self = this;
                opts.interval = setInterval(function() {
                    var et = self.text();
                    self.text((et.length - i) < 3 ? et + '.' : t.substring(0,i));
                }, opts.ellipsis.time);
            }
        },
        type: {
            time: 100,
            run: function(opts) {
                this.css('width', $.boxModel ? this.width() : this.innerWidth());
                var t = opts.text, self = this;
                opts.interval = setInterval(function() {
                    var e = self.text(), el = e.length;
                    self.text(el == t.length ? t.charAt(0) : t.substring(0, el+1));
                }, opts.type.time);
            }
        },

        // functions
        toggle: function(opts) {
            var old = this.data('loading');
            if (old) {
                old.off.call(this, old, opts);
            } else {
                opts.on.call(this, opts);
            }
        },
        on: function(opts) {
            opts.parent = this;
            opts.element = opts.create.call(this, opts);
            if (opts.img) {
                opts.initImg.call(this, opts);
            } else {
                opts.init.call(this, opts);
            }
        },
        initImg: function(opts) {
            var self = this;
            opts.element.addClass(opts.withImg);
            opts.img.bind('load', function() {
                opts.init.call(self, opts);
            });
        },
        init: function(opts) {
            opts.place.call(opts.element, opts);
            this.data('loading', opts);
            if (opts.pulse) {
                opts[opts.pulse].run.call(opts.element, opts);
            }
        },
        create: function(opts) {
            var el = $(opts.html).addClass(opts.classname).css(opts.css).appendTo(this);
            if (opts.img) {
                opts.img = $('<img src="'+opts.img+'"/>').appendTo(el);
            } else if (opts.text) el.text(opts.text);
            return el;
        },
        place: function(opts) {
            var pos = opts.align;
            if (typeof pos != "object") {
                var v = 'top', h = 'left', s;
                if (pos != 'top-left') {
                    s = pos.split('-');
                    if (s.length == 1) {
                        v = h = s[0];
                    } else {
                        v = s[0]; h = s[1];
                    }
                }
                if (!this.hasClass(v)) this.addClass(v);
                if (!this.hasClass(h)) this.addClass(h);
                pos = opts.calc.call(this, v, h, opts);
            }
            this.css(pos);
        },
        calc: function(v, h, opts) {
            var p = opts.parent, pos = p.offset();
            if (v != 'top') {
                var d = p.outerHeight() - this.innerHeight();
                if (v == 'center') {
                    d /= 2;
                } else if (v != 'bottom') {
                    d = 0;
                }
                pos.top += d;
            }
            if (h != 'left') {
                var d = p.outerWidth() - this.innerWidth();
                if (h == 'center') {
                    d /= 2;
                } else if (h != 'right') {
                    d = 0;
                }
                pos.left += d;
            }
            return pos;
        },
        off: function(old, opts) {
            this.data('loading', null);
            if (old.pulse && old[old.pulse].end) {
                old[old.pulse].end.call(this, old, opts);
            }
            if (old.interval) clearInterval(old.interval);
            if (old.timeout) clearTimeout(old.timeout);
            old.element.remove();
        }
    });

})(jQuery);
